﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Epsilon.Expr;

namespace Epsilon.Typing
{
    public class Context
    {
        private Dictionary<VarExpr, Stack<TypeLabel>> map = new Dictionary<VarExpr, Stack<TypeLabel>>();
        public RecordStore Records { get; set; }
        public string Self { get; set; }
        public SecLabel PC { get; set; }

        public Context(string self, RecordStore r)
        {
            Self = self;
            Records = r;
            PC = new SecBase(null, null);
        }

        public void AddMap(IPattern p, TypeLabel t)
        {
            if (p is VarExpr)
                AddMap((VarExpr)p, t);
            else if (p is PatternTuple)
                AddMap((PatternTuple)p, t);
            else if (p is PatternRec)
                AddMap((PatternRec)p, t);
            else
                throw new TypingException("Invalid pattern found");
        }

        public void AddMap(VarExpr v, TypeLabel t)
        {
            if (map.ContainsKey(v))
            {
                map[v].Push(t);
            }
            else
            {
                Stack<TypeLabel> s = new Stack<TypeLabel>();
                s.Push(t);
                map[v] = s;
            }
        }

        public void AddMap(PatternTuple p, TypeLabel t)
        {
            TupleType tt = t.Type as TupleType;
            PidTuple pmax = (t.MaxPidLabel is PidTuple) ? (PidTuple)t.MaxPidLabel : 
                (t.MaxPidLabel is PidRTuple) ? (PidTuple)PidUtil.FromRestricted((PidRTuple)t.MaxPidLabel) : null;
            PidTuple pmin = (t.MinPidLabel is PidTuple) ? (PidTuple)t.MinPidLabel :
                (t.MinPidLabel is PidRTuple) ? (PidTuple)PidUtil.FromRestricted((PidRTuple)t.MinPidLabel) : null;
            SecTuple sec = (t.SecLabel is SecTuple) ? (SecTuple)t.SecLabel :
                (t.SecLabel is SecRTuple) ? (SecTuple)SecUtil.FromRestricted((SecRTuple)t.SecLabel) : null;

            if (tt == null || pmax == null || pmin == null || sec == null)
                throw new TypingException("Pattern does not match type of argument");

            int c = p.Patterns.Count;
            if (tt.Types.Count != c || pmax.Labels.Count != c || 
                pmin.Labels.Count != c || sec.Labels.Count != c)
                throw new TypingException("Length of pattern does not match length of labels");

            for (int i = 0; i < c; i++) 
                AddMap(p.Patterns[i], new TypeLabel(
                    tt.Types[i], pmax.Labels[i], pmin.Labels[i], sec.Labels[i]));
        }

        public void AddMap(PatternRec p, TypeLabel t)
        {
            if (!Records.ContainsRecord(p.Name))
                throw new TypingException("Record " + p.Name + " has not been defined");

            TupleType tt = t.Type as TupleType;
            PidTuple pmax = (t.MaxPidLabel is PidTuple) ? (PidTuple)t.MaxPidLabel :
                (t.MaxPidLabel is PidRTuple) ? (PidTuple)PidUtil.FromRestricted((PidRTuple)t.MaxPidLabel) : null;
            PidTuple pmin = (t.MinPidLabel is PidTuple) ? (PidTuple)t.MinPidLabel :
                (t.MinPidLabel is PidRTuple) ? (PidTuple)PidUtil.FromRestricted((PidRTuple)t.MinPidLabel) : null;
            SecTuple sec = (t.SecLabel is SecTuple) ? (SecTuple)t.SecLabel :
                (t.SecLabel is SecRTuple) ? (SecTuple)SecUtil.FromRestricted((SecRTuple)t.SecLabel) : null;

            if (tt == null || pmax == null || pmin == null || sec == null)
                throw new TypingException("Pattern does not match type of argument");

            for (int i = 0; i < p.Fields.Count; i++)
            {
                int index = Records.IndexOfField(p.Name, p.Fields[i].Name);
                if (index < 0)
                    throw new TypingException("Record " + p.Name + " does not contain a field " +
                        "named " + p.Fields[i].Name);

                // first element of tuples are ignored
                // if tt.Types.Count = c then index must be between 1 and c-1
                // so 0-based index between 0 and c-2
                if (index >= tt.Types.Count - 1 || index >= pmax.Labels.Count - 1 ||
                    index >= pmin.Labels.Count - 1 || index >= sec.Labels.Count - 1)
                    throw new TypingException("Invalid record type");

                AddMap(p.Fields[i].Pattern, new TypeLabel(
                    tt.Types[index + 1], pmax.Labels[index + 1], pmin.Labels[index + 1], sec.Labels[index + 1]));
            }
        }

        public void RemoveMap(IPattern p)
        {
            if (p is VarExpr)
                RemoveMap((VarExpr)p);
            else if (p is PatternTuple)
                RemoveMap((PatternTuple)p);
            else if (p is PatternRec)
                RemoveMap((PatternRec)p);
            else
                throw new TypingException("Invalid pattern found");
        }

        public void RemoveMap(VarExpr v)
        {
            if (!map.ContainsKey(v))
                return;
            map[v].Pop();
            if (map[v].Count == 0)
                map.Remove(v);
        }

        public void RemoveMap(PatternTuple p)
        {
            for (int i = p.Patterns.Count - 1; i >= 0; i--)
                RemoveMap(p.Patterns[i]);
        }

        public void RemoveMap(PatternRec p)
        {
            for (int i = p.Fields.Count - 1; i >= 0; i--)
                RemoveMap(p.Fields[i].Pattern);
        }

        public TypeLabel Get(VarExpr v)
        {
            if (map.ContainsKey(v))
                return map[v].Peek();
            return null;
        }
    }
}
